import * as cornerstoneTools from "@cornerstonejs/tools";
import type { Types } from "@cornerstonejs/tools";
import { MPR_VIEWPORT_ID as VIEWPORT_ID } from "@/services/types";

const {
  LengthTool,
  StackScrollMouseWheelTool,
  StackScrollTool,
  PanTool,
  ZoomTool,
  TrackballRotateTool,
  WindowLevelTool,
  CrosshairsTool,
  Enums: csToolsEnums,
} = cornerstoneTools;

const { MouseBindings, KeyboardBindings } = csToolsEnums;

let registered = false;

const viewportColors: { [key: string]: string } = {
  [VIEWPORT_ID.CT_AXIAL]: "rgb(200, 0, 0)",
  [VIEWPORT_ID.CT_SAGITTAL]: "rgb(200, 200, 0)",
  [VIEWPORT_ID.CT_CORONAL]: "rgb(0, 200, 0)",
};

const viewportReferenceLine: string[] = [
  VIEWPORT_ID.CT_AXIAL,
  VIEWPORT_ID.CT_SAGITTAL,
  VIEWPORT_ID.CT_CORONAL,
];

function getReferenceLineColor(viewportId: string) {
  return viewportColors[viewportId];
}

function getViewportReferenceLine(viewportId: string) {
  const index = viewportReferenceLine.indexOf(viewportId);
  return index !== -1;
}

export type ToolBinding = {
  // A base tool to register.  Should only be defined once per tool
  tool?: unknown;
  // The tool name to base this on
  baseTool?: string;
  // The configuration to register with
  configuration?: Record<string, unknown>;
  // Sets to passive initially
  passive?: boolean;
  // Initial bindings
  bindings?: Types.IToolBinding[];
};

/**
 * Adds navigation bindings to the given tool group.  Registers the basic
 * tool with CS Tools if register is true.
 *
 * Adds:
 * * Pan on Right or Primary+Ctrl
 * * Zoom on Middle, Primary+Shift
 * * Stack Scroll on Mouse Wheel, Primary+Alt
 * * Length Tool on fourth button
 *
 * Also allows registering other tools by having them in the options.toolMap with configuration values.
 */
export default function addManipulationBindings(
  toolGroup: Types.IToolGroup,
  options: {
    enableShiftClickZoom?: boolean;
    is3DViewport?: boolean;
    toolMap?: Map<string, ToolBinding>;
  } = {}
) {
  const {
    is3DViewport = false,
    enableShiftClickZoom = true,
    toolMap = new Map(),
  } = options;

  if (!registered) {
    cornerstoneTools.addTool(PanTool);
    cornerstoneTools.addTool(ZoomTool);
    cornerstoneTools.addTool(TrackballRotateTool);
    cornerstoneTools.addTool(LengthTool);
    cornerstoneTools.addTool(StackScrollTool);
    cornerstoneTools.addTool(StackScrollMouseWheelTool);
    cornerstoneTools.addTool(WindowLevelTool);
    for (const [, config] of toolMap) {
      if (config.tool) {
        cornerstoneTools.addTool(config.tool);
      }
    }
  }

  registered = true;

  toolGroup.addTool(PanTool.toolName);
  // Allow significant zooming to occur
  toolGroup.addTool(ZoomTool.toolName, {
    minZoomScale: 0.001,
    maxZoomScale: 4000,
  });
  if (is3DViewport) {
    toolGroup.addTool(TrackballRotateTool.toolName, {
      rotateSampleDistanceFactor: 2,
    });
  } else {
    toolGroup.addTool(StackScrollMouseWheelTool.toolName);
  }
  toolGroup.addTool(LengthTool.toolName);
  toolGroup.addTool(StackScrollTool.toolName);
  toolGroup.addTool(WindowLevelTool.toolName);

  toolGroup.setToolActive(WindowLevelTool.toolName, {
    bindings: [
      {
        mouseButton: MouseBindings.Secondary,
      },
      {
        numTouchPoints: 1,
        modifierKey: KeyboardBindings.Alt,
      },
      {
        mouseButton: MouseBindings.Primary, // Shift Left Click
        modifierKey: KeyboardBindings.Alt,
      },
    ],
  });

  toolGroup.setToolActive(PanTool.toolName, {
    bindings: [
      {
        mouseButton: MouseBindings.Auxiliary,
      },
      {
        numTouchPoints: 1,
        modifierKey: KeyboardBindings.Ctrl,
      },
    ],
  });

  if (enableShiftClickZoom === true) {
    toolGroup.setToolActive(ZoomTool.toolName, {
      bindings: [
        {
          mouseButton: MouseBindings.Primary, // Shift Left Click
          modifierKey: KeyboardBindings.Shift,
        },
      ],
    });
  }
  // Need a binding to navigate without a wheel mouse
  toolGroup.setToolActive(StackScrollTool.toolName, {
    bindings: [
      {
        mouseButton: MouseBindings.Primary,
      },
      {
        numTouchPoints: 1,
      },
      {
        mouseButton: MouseBindings.Primary, // Shift Left Click
        modifierKey: KeyboardBindings.CtrlAlt,
      },
    ],
  });

  const isMobile = window.matchMedia("(any-pointer:coarse)").matches;
  toolGroup.addTool(CrosshairsTool.toolName, {
    getReferenceLineColor,
    getReferenceLineControllable: getViewportReferenceLine,
    getReferenceLineDraggableRotatable: getViewportReferenceLine,
    getReferenceLineSlabThicknessControlsOn: getViewportReferenceLine,
    mobile: {
      enabled: isMobile,
      opacity: 0.8,
      handleRadius: 9,
    },
  });

  toolGroup.setToolActive(CrosshairsTool.toolName, {
    bindings: [
      {
        mouseButton: MouseBindings.Primary,
        modifierKey: KeyboardBindings.Ctrl,
      },
      {
        numTouchPoints: 1,
        modifierKey: KeyboardBindings.Ctrl,
      },
    ],
  });

  toolGroup.setToolActive(LengthTool.toolName, {
    bindings: [
      {
        mouseButton: MouseBindings.Primary,
        modifierKey: KeyboardBindings.ShiftCtrl,
      },
      {
        numTouchPoints: 1,
        modifierKey: KeyboardBindings.ShiftCtrl,
      },
    ],
  });

  if (is3DViewport) {
    toolGroup.setToolActive(TrackballRotateTool.toolName, {
      bindings: [
        {
          mouseButton: MouseBindings.Primary,
        },
      ],
    });
  } else {
    toolGroup.setToolActive(StackScrollMouseWheelTool.toolName);
  }

  // Add extra tools from the toolMap
  for (const [toolName, config] of toolMap) {
    if (config.baseTool) {
      if (!toolGroup.hasTool(config.baseTool)) {
        toolGroup.addTool(
          config.baseTool,
          toolMap.get(config.baseTool)?.configuration
        );
      }
      toolGroup.addToolInstance(
        toolName,
        config.baseTool,
        config.configuration
      );
    } else if (!toolGroup.hasTool(toolName)) {
      toolGroup.addTool(toolName, config.configuration);
    }
    if (config.passive) {
      // This can be applied during add/remove contours
      toolGroup.setToolPassive(toolName);
    }
    if (config.bindings || config.selected) {
      toolGroup.setToolActive(
        toolName,
        (config.bindings && config) || {
          bindings: [{ mouseButton: MouseBindings.Primary }],
        }
      );
    }
  }
}
